// gulp入口文件, gulp的任务函数都是异步的,  运行gulp任务：  yarn  gulp  任务名
// exports.default  任务名是default，默认任务的执行：yarn  gulp
/**
 * 第一种写法：                               第二种写法：
 * exports.任务名1 = done => {               const 任务名2 = done => {
 *    done()// 表示任务完成                       done()
 * }                                        }
 *                                          exports
 *-------------------------------------------------------------------------
 * gulp 4.0 之前 ：
 *  const gulp = require('gulp')
 *  gulp.task('shang', done => {
 *      console.log('shang task working~')
 *      done()
 *  })
 */

/**
 * const { series, parallel } = require('gulp') // 还有一个watch监听方法
 * const 任务后, 导出任务：module.exports = { task1,task2 } 
 *  const 定义任务名
 *  exports.shang = series(task1,task2,task3...) 按顺序执行
 *  exports.bei = parallel(task1,task2,task3...) 多任务同时执行
 */

//  promise同样适用，回调错误优先  ，提示错误信息，停止任务
/**   done(new Error('你想打印的错误信息'))
 *    成功回调：return Promise.resolve()
 *    错误回调：return Promise.reject(new Error('task failed'))
 */

/** fs模块有两个方法，读取流，写入流，用于后续文件的编译转换
 * const fs = require('fs')
 * const { Transform } = require('stream')  转换流
 *
  exports.stream = done => {
    const readStream = fs.createReadStream('normalize.css') （你要读取的文件）
    const writeStream = fs.createWriteStream('normalize.min.css')   （你想写入的文件）

    const transformStream = new Transform({  // 核心转换过程，chunk 是读取内容
    transform: (chunk, encoding, callback) => {
      const input = chunk.toString()
      // 去除空白字符，替换掉注释
      const output = input.replace(/\s+/g, '').replace(/\/\*.+?\*\//g, '')
      // 错误优先，第一个参数，错误对象，没有写null，返回输出内容
      callback(null, output)
    }
  })
  return readStream.pipe(transformStream).pipe(writeStream)

  // readStream.pipe(writeStream)    a.pipe(b) 意思：把 a 给b 处理下，起个文件复制的作用
  // return readStream  //  pipe() 处理过后再return 出去

    //  模拟实现 是stream当中都有个end 事件，读取流结束后触发，gulp就知道完成了
    // read.on('end', () => {
    //   done()
    // })
  }
  -----------------------------------------------------------------------------------
 */

/** gulp  API 读取文件流 src('文件目录'), 写入文件流 dest('dist') 最常见的打包后文件名
 *  const { src, dest } = require('gulp')
 *  const del = require('del')  非gulp插件，先定义
 *  const browserSync = require('browser-sync')  非gulp插件，先定义
 *
  需要安装的gulp 插件：
  yarn add gulp --dev  如此可以添加gulpfile.js 文件
  yarn add gulp--clean-css --dev, require 载入，如下 const 内容
  yarn add gulp--rename --dev, require 载入，如下 const 内容
  yarn add gulp-sass --dev    样式文件转换
  yarn add gulp-babel --dev    js文件转换
  yarn add @babel/core  @babel/preset-env --dev
  yarn add gulp-swig --dev  模板引擎，HTML转换
  yarn add gulp-imagemin -dev  图片压缩,字体里面也有svg, 字体压缩也用这个
  yarn add del --dev   非gulp插件，只是在gulp中可以使用, 且是个promise方法，gulp支持
  yarn add gulp-load-plugins --dev // require操作太多的话，可不单独引入插件，自动加载全部plugin
  yarn add browser-sync --dev // 创建开发服务器，支持热更新，非gulp插件
  yarn add gulp-useref --dev // 识别根目录下的路径文件
  yarn add gulp-htmlmin  gulp-uglify gulp-clean-css --dev // 三种文件压缩插件
  yarn add gulp-if --dev  // 判断读取文件流的 问价类型

    
    // const cleanCSS = require('gulp-clean-css') // 压缩css代码 gulp-sass是样式文件转换
    const rename = require('gulp-rename')

    const loadPlugins = require('gulp-load-plugins')// 导出的是个方法，所有插件变成它的属性
    const plugins = loadPlugins()
    // gulp-sass ------->  plugins.sass,去掉gulp,后面驼峰命名法
    cost bs = browserSync.create() // 创建开发服务器，后面单独定义到一个serve 任务中取

    // 都是函数,  使用了 plugins，下面这些gulp单独插件 全部去掉，快速重命名，右键
    // const sass = require('gulp-sass')   pipe( sass() ) , 文件会覆盖，不识别_开头的css文件
    // const babel = require('gulp-babel') pipe( babel() )，babel是平台，preset是一些插件集合，真正做转换的
    // 需要手动命令添加 @babel/core  @babel/preset-env  // ECMAScript新特性都会转换
    // babel() 里 { presets: ['@babel/preset-env'] } 不传，转换不生效，
    // const swig = require('gulp-swig')          // pipe( swig( {data} ) )
    // const imagemin = require('gulp-imagemin')  // pipe( imagemin() )

    exports style = () => {
      return src('src/assets/style/*.css', { base: 'src' })
      .pipe(sass()) // 或者 .pipe(cleanCSS())   
      .pipe(rename({ extname: '.min.css' })) //  重命名,extname参数指定重命名的扩展
      .pipe(dest('dist'))
    }
    exports script = () => {
      return src('src/assets/api/*.js', { base: 'src' })
      .pipe( babel({ presets: ['@babel/preset-env'] }) )  
      .pipe(rename({ extname: '.min.css' })) //  重命名,extname参数指定重命名的扩展
      .pipe(dest('dist'))
    }
    const page = () => {
      return src('src/*.html', { base: 'src' })
        //.pipe(swig()) // 单独引用gulp的部分模块 就这么用，集体使用 用下面的， data需要最上面定义
        // { data: data } 可简写为 data
        .pipe(plugins.swig({ data, defaults: { cache: false } })) // 防止模板引擎的缓存机制导致页面不能及时更新
        .pipe(dest(config.build.temp))
        .pipe(bs.reload({ stream: true })) // 这种事实时渲染更常见，那bs.init() 里面的file 就不需要了
    }

    const compile = parallel(style, script, page, image, font)
    // module.exports = { compile }  // 运行   yarn  gulp  compile
    const build = parallel(compile, extra就是public)
    module.exports = { build }  // 所有文件的构建
    
    useref  文件引用处理,解决线上找不到node_modules文件下的文件，
    有规律的构建注释，开始有 build: css文件类型js 采取指定路径 就是文件最终打包地址 ， 结束有 endbuild
    最终合并到一个文件  
 *
 */

const { src, dest, parallel, series, watch } = require('gulp')

const del = require('del')
const browserSync = require('browser-sync')
// 也可以直接const browsersync=require("browser-sync").create()
const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()
const bs = browserSync.create()
const cwd = process.cwd() // 获取当前项目的工作目录
// 设置默认配置项，供于抽象目录 便于修改文件名，便于维护
let config = {
  build: {
    src: 'src',
    dist: 'dist',
    temp: 'temp',
    public: 'public',
    paths: {
      styles: 'src/assets/styles/*.scss',
      scripts: 'src/assets/scripts/*.js',
      pages: '*.html',
      images: 'src/assets/images/**',
      fonts: 'src/assets/fonts/**'
    }
  }
}

try {
  const loadConfig = require(`${cwd}/pages.config.js`) // 根目录下自己新建的配置文件pages.config.js
  config = Object.assign({}, config, loadConfig)
} catch (e) { }

// 动态设置 可以新建一个 比如  shang.config.js   module.exports = { build：{}，data：{} 的相关信息，data里require的package.json文件，会指向bin目录的文件，添加bin/你的模块名.js文件，让gulp-处理运行 }
/** 
 * #!/usr/bin/env node   指定node环境必不可少，添加任务传参的路径，运行gulp-cli
    process.argv.push('--cwd')
    process.argv.push(process.cwd())
    process.argv.push('--gulpfile')
    process.argv.push(require.resolve('..'))
    require('gulp/bin/gulp')
    然后  package.json文件 添加 bin，一并上传到npm 上，用于日后复用
    "files": [
      "lib",
      "bin"
    ],
 * 
*/
// const data = {
//   // 像菜单路由
//   menus: [
//     {
//       name: 'Home',
//       icon: 'aperture',
//       link: 'index.html'
//     },
//     {
//       name: 'Contact',
//       link: '#',
//       children: [
//         {
//           name: 'Twitter',
//           link: 'https://twitter.com/w_zce'
//         },
//         {
//           name: 'About',
//           link: 'https://weibo.com/zceme'
//         },
//         {
//           name: 'divider'
//         },
//         {
//           name: 'About',
//           link: 'https://github.com/zce'
//         }
//       ]
//     }
//   ],
//   pkg: require('./package'),
//   data: new Date()
// }

// 删除文件
const clean = () => {// 清空 dist temp 目录里的文件。 del()接收一个字符串数组
  // return del(['dist', 'temp'])
  return del([config.build.dist, config.build.temp])
}
// 文件转换
const style = () => {
  // return src('src/assets/styles/*.scss', { base: 'src' }) // {base: 'src'} 定义基础路径，原路转换文件
  return src(config.build.paths.styles, { base: config.build.src, cwd: config.build.src })
    .pipe(plugins.sass({ outputStyle: 'expanded' })) // 样式文件的 } 符号 允许换行，而非在最后一个样式后面
    .pipe(dest(config.build.temp)) // .pipe(dest('temp'))
    .pipe(bs.reload({ stream: true }))
}
const script = () => {
  return src(config.build.paths.scripts, { base: config.build.src, cwd: config.build.src })
    // .pipe(plugins.babel({ presets: ['@babel/preset-env'] }))
    .pipe(plugins.babel({ presets: [require('@babel/preset-env')] }))
    .pipe(dest(config.build.temp))
    .pipe(bs.reload({ stream: true }))
}
const page = () => {
  return src(config.build.paths.pages, { base: config.build.src, cwd: config.build.src })
    // .pipe(plugins.swig({ data, defaults: { cache: false } })) // 防止模板缓存导致页面不能及时更新
    .pipe(plugins.swig({ data: config.data, defaults: { cache: false } }))
    .pipe(dest(config.build.temp))
    .pipe(bs.reload({ stream: true }))
}
const image = () => {
  return src(config.build.paths.images, { base: config.build.src, cwd: config.build.src })
    .pipe(plugins.imagemin())
    .pipe(dest(config.build.dist))
}
const font = () => {
  return src(config.build.paths.fonts, { base: config.build.src, cwd: config.build.src })
    .pipe(plugins.imagemin())
    .pipe(dest(config.build.dist))
}
// public 文件转换
const extra = () => {
  return src('public/**', { base: 'public' })
    .pipe(dest(config.build.dist))
}
// 开发服务器
const serve = () => {
  // watch() 修改源代码，浏览器也实时更新
  watch(config.build.paths.styles, { cwd: config.build.src }, style)
  watch(config.build.paths.scripts, { cwd: config.build.src }, script)
  watch(config.build.paths.pages, { cwd: config.build.src }, page)
  // watch('src/assets/styles/*.scss', style)
  // watch('src/assets/scripts/*.js', script)
  // watch('src/*.html', page)

  // 下面三个注释 是因为在开发阶段 没有意义，发布上线才需要，不注释会降低开发构建效率，因为不影响页面呈现
  // bs.init() 里面对象中的server对象，baseDir: 'dist' 改成 baseDir: ['dist', 'src', 'public'],
  // watch('src/assets/images/**', image)
  // watch('src/assets/fonts/**', font)
  // watch('public/**', extra)
  // watch([ //  可以接受数组
  //   'src/assets/images/**',
  //   'src/assets/fonts/**',
  //   'public/**'
  // ], bs.reload)
  watch([
    config.build.paths.images,
    config.build.paths.fonts
  ], { cwd: config.build.src }, bs.reload)

  bs.init({// 初始化 web服务器的相关配置
    notify: false,// 浏览器启动单饿瞬间，右上角的提示，关掉
    port: 2080, // 默认端口3000
    // open: false, // 执行  yarn gulp serve  会自动打开浏览器，看到相应效果，dist修改，自动更新
    // 如果html文件里 <link rel='stylesheet' href='node_modules/*/css'> 这个根目录下的不识别，需单独添加路由
    // files: 'dist/**', // 监听改变的文件，方便刷新页面 任务重更多添加 bs.reload
    server: { // 指定网站根目录 dist 可定义其他名字 比如temp，  baseDir: 'dist'
      // baseDir: ['temp', 'src', 'public'],
      baseDir: [config.build.temp, config.build.dist, config.build.public],
      routes: { // 让 html 文件 识别 根目录下的样式文件，优先baseDir的配置
        '/node_modules': 'node_modules'// 开发阶段没有问题，有路由映射，但是线上会找不到，可以用 useref
      }
    }
  })
}

// 用在上线之前的任务，不在开发阶段，会特别慢，使用前需要compile基础构建任务执行一下
const useref = () => {
  // return src('dist/*.html', { base: 'dist' })
  return src(config.build.paths.pages, { base: config.build.temp, cwd: config.build.temp })
    // useref({}) 对象指定搜索路径，dist和根目录，合并生成新文件
    .pipe(plugins.useref({ searchPath: [config.build.temp, '.'] }))
    /** 
    * 三种文件 html  js  css, 需要三个压缩插件：之后还需要 判断读取流的文件类型的插件
    * yarn add gulp-htmlmin  gulp-uglify gulp-clean-css --dev
    * yarn add gulp-if --dev
    * htmlmin 只压缩空白字符，换行符默认不删除，要删除加对象 collapseWhitespace: true,
    */
    .pipe(plugins.if(/\.js$/, plugins.uglify()))
    .pipe(plugins.if(/\.css$/, plugins.cleanCss()))// gulp-clean-scc ===> cleanCss驼峰命名法
    .pipe(plugins.if(/\.html$/, plugins.htmlmin({
      collapseWhitespace: true, // 不删除换行符导致的空格
      minifyCSS: true, //  删除行内style样式的换行空格，同理 js
      minifyJS: true
    })))
    .pipe(dest(config.build.dist))
  //  边读边写容易有问题，可以切换dest 文件名 / 不然就打包放temp，最终到dist
  // image  font  extra 本就是上线才压缩的，不需要改dest路径，只有会被useref影响的才需要修改
  // temp文件夹里应该只有html css  js文件
}

const compile = parallel(style, script, page) // 基础构建
const build = series(clean, parallel(series(compile, useref), image, font, extra)) // 打包执行的构建，.gitignore 文件需要添加  dist temp
const dev = series(compile, serve) // 开发执行的构建
// 想单独执行就放里面，不影响
// 可以把命令添加至package.json 的 script:{ build: gulp build } 中去
module.exports = {
  clean,
  build,
  dev,
}